Note: Occasionally, a mandatory to-one relationship doesn't resolve to a destination object. For example, suppose your application's Movie database contains legacy data for which relational integrity constraints weren't strictly enforced. As a result, some Movies don't have corresponding Studios even though the Movies-to-Studios relationship is mandatory. The techniques for handling these errant to-one relationships are the same as those for handling optional to-one relationships.
You can model an optional to-one relationship many different ways, depending on how you represent the relationship in the database-as a foreign key to primary key join or as a primary key to primary key join.
Note: For to-one relationships, Enterprise Objects Framework doesn't support primary key to foreign key joins. The destination join attribute in a to-one relationship must be the destination entity's primary key.
Using a foreign key to primary key join, you include the destination row's primary key in the source row. For example, in the relationship shown in Figure 24, the creditCard relationship's source table (MEMBER) has a foreign key (CARD_NUMBER) to the destination table (CREDIT_CARD). Using this approach, you can model an optional
to-one relationship as a true to-one relationship just as you would model a mandatory to-one relationship.
Figure 24. Storing a Foreign Key in the Source Table
A foreign key to primary key join is the best way to model a to-one relationship for use with Enterprise Objects Framework. If you have control over the design of the database schema, use foreign key to primary key joins for to-one relationships whenever possible.
Alternatively, you can use a primary key to primary key join that includes the source's primary key in the destination table. For example, in the relationship shown in Figure 25, the talentPhoto relationship's destination table (TALENT_PHOTO) has a foreign key (TALENT_ID) to the source table (TALENT). For reasons described below, this arrangement requires special handling.
Figure 25. Storing a Foreign Key in the Destination Table
When Enterprise Objects Framework fetches an enterprise object, it attempts to assign destination objects to any of the object's to-one relationships. If a destination object hasn't already been fetched, Enterprise Objects Framework creates a fault to stand in for the destination object until it is actually needed. (For more information on faulting, see the chapter "Behind the Scenes".)
On the other hand, Enterprise Objects Framework can't detect that a primary key to primary key relationship doesn't have a destination. For example, Enterprise Objects Framework can't tell that a TALENT record doesn't have a corresponding TALENT_PHOTO record until it tries to fetch a TALENT_PHOTO with the same TALENT_ID value and fails. Consequently, in a primary key to primary key relationship, Enterprise Objects Framework always assigns a fault if a corresponding destination object hasn't been fetched. If no such destination exists, Enterprise Objects Framework throws an exception when it tries to resolve the fault.
An optional primary key to primary key relationship (such as talentPhoto) can be handled in a number of ways:
This approach is a good choice when the destination object contains what are conceptually attributes of the source object. For example, conceptually photo is an attribute of a Talent object. It's implemented using a to-one relationship for performance reasons. (Photo data is very large, and isn't fetched unless-and until-it's needed.)
Figure 26. A Destination Object with null-Valued Attributes
This approach doesn't require any code. In the Advanced Relationship Inspector for the Talent entity's talentPhoto relationship, you simply set the relationship to propagate primary key. Propagate primary key tells Enterprise Objects Framework to propagate the primary key of the source entity into newly inserted objects in the destination entity (instead of generating a primary key value for the destination). With this configuration, Enterprise Objects Framework inserts a new Talent object, it inserts a corresponding TalentPhoto object if the Talent object doesn't already have one assigned to it.
You can design your enterprise object's API to hide the to-many implementation. For example, suppose that Talent's talentPhoto relationship was modeled as a to-many. To design a Talent enterprise object that acts as if its talentPhoto relationship is an optional to-one, you could name the to-many relationship (and the corresponding instance variable) "_talentPhotoArray" and implement the following two accessor methods:
public void setTalentPhoto(TalentPhoto talentPhoto)In Objective-C:
{
willChange();
_talentPhotoArray.removeAllObjects();
if (talentPhoto != null)
_talentPhotoArray.addObject(talentPhoto);
}
public TalentPhoto talentPhoto()
{
willRead();
if (_talentPhotoArray.count() > 0)
return _talentPhotoArray.objectAtIndex(0);
return null;
}
- (void)setTalentPhoto:(TalentPhoto *)talentPhoto
{
[self willChange];
[_talentPhotoArray removeAllObjects];
if (_talentPhotoArray)
[_talentPhotoArray addObject:talentPhoto];
}
- (id)talentPhoto
{
if ([_talentPhotoArray count])
return [_talentPhotoArray objectAtIndex:0];
return nil;
}
public TalentPhoto talentPhoto()In Objective-C:
{
try {
// If the receiver is a fault, sending it a willRead
// message attempts to resolve it. If the
// corresponding row doesn't exist in the database,
// an exception is thrown.
talentPhoto.willRead();
} catch (NSException e) {
talentPhoto = null;
}
return talentPhoto;
}
- (TalentPhoto *)talentPhotoSending willRead (or self in Objective-C) to a fault triggers it to fetch its corresponding enterprise object. If a Talent instance doesn't have a corresponding TalentPhoto, sending willRead to the talentPhoto property throws an exception. In the talentPhoto method above, the exception handler simply sets the property to null (first autoreleasing the talentPhoto fault in Objective-C).
{
NS_DURING
// If the receiver is a fault, sending it a self
// message attempts to resolve it. If the
// corresponding row doesn't exist in the database,
// an exception is raised.
[talentPhoto self];
NS_HANDLER
[talentPhoto autorelease];
talentPhoto = nil;
NS_ENDHANDLER
return talentPhoto;
}
For example, to handle mandatory to-one relationships with errant data (source rows that don't have corresponding destinations), you could implement the delegate method to insert the empty object, thereby supplying the missing destination object:
public boolean databaseContextFailedToFetchObject(In Objective-C:
EODatabaseContext context,
Object object,
EOGlobalID gid)
{
// Perform a check to determine whether to intervene
if (...) {
// Set values in your object (if necesssary).
object.editingContext().insertObject(object);
return false;
}
return true;
}
- (BOOL)databaseContext:(EODatabaseContext *)contextIn the above implementations, the delegate method first checks to see if it should intervene. For example, the method might check to see if object is an instance of the TalentPhoto class. If the delegate determines that object represents a destination object that's missing from the database, the delegate queues object for insertion into the database by inserting it into its editing context. It returns false (NO in Objective-C) indicating that the delegate has handled the error and that the EODatabaseContext shouldn't throw an exception.
failedToFetchObject:(id)object
globalID:(EOGlobalID *)gid
{
// Perform a check to determine whether to intervene
if (...) {
// Set values in your object (if necesssary).
[[object editingContext] insertObject:object];
return NO;
}
return YES;
}
To model a many-to-many relationship in a database, you have to create an intermediate table (also known as a correlation or join table). For example, the database for employees and projects might have EMPLOYEE, PROJECT, and EMP_PROJ tables, where EMP_PROJ is the correlation table. The appendix "Entity-Relationship Modeling" provides more information on the tables behind a many-to-many relationship.
Given the relational database representation of a many-to-many, how do you get the object model you want? You don't want to see evidence of the correlation table in your object model, and you don't want to write code to maintain database correlation rows. With Enterprise Objects Framework, you don't have to. Simply use flattened relationships as described in the chapter "Using EOModeler" to hide your correlation tables.
A model with the following features has the effect of hiding the EMP_PROJ correlation table from its object model altogether:
However, what do you do when a correlation table contains extra attributes that are interesting? For example, the MOVIE_ROLE table in the sample Movies database is a correlation table between movies and the actors who star in them. In addition to foreign keys for MOVIE and TALENT, the MOVIE_ROLE table also contains the name of the role the actor plays in the film. In this case, MovieRole enterprise objects actually have a place in the object model even though they're fetched from a correlation table.
If you want to programmatically access both a Movie's roles and its actors directly from the Movie object, you should do the following:
Instead, if you create an actors method that traverses the object graph through the Movie's MovieRole objects, you avoid any consistency problems.
For display purposes, you don't even need an accessor method to bypass a correlation object. Instead, you can use key paths. For example, you can use the key path roles.talent to access a Movie's Talent objects in a master-detail configuration between a Movie EODisplayGroup and a Talent EODisplayGroup.
Table of Contents
Next Section